package site.kitcheng.springboottest.api.controller;

import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.errors.*;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import site.kitcheng.springboottest.domain.FileChunk;
import site.kitcheng.springboottest.infra.config.minio.MinioPropertiesConfig;
import site.kitcheng.springboottest.infra.constant.BaseConstant;
import site.kitcheng.springboottest.infra.utils.FileSHA256;
import site.kitcheng.springboottest.infra.utils.Result;
import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * ClassName: FileUplaod
 * Package: site.kitcheng.springboottest.api.controller
 * Description:
 *
 * @Author jiangCheng
 * @Create 2025/1/24 11:29
 * @Version 1.0
 */
@RestController
@RequestMapping("file-upload")
@CrossOrigin
public class FileUpload {


    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    MinioClient minioClient;

    @Autowired
    private MinioPropertiesConfig minioPropertiesConfig;

    @GetMapping("/upload/status/{id}")
    public Result uploadStatus(@PathVariable String id) {
        FileChunk fileUpload = new FileChunk();
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(id);
        return Result.success(entries);
    }

    @PostMapping("/chunk")
    public Result<FileChunk> fileUpload(MultipartFile file, FileChunk fileUpload) throws IOException, NoSuchAlgorithmException{
        String property = System.getProperty(BaseConstant.USER_DIR);
        String path = property + File.separator + BaseConstant.PATH+ File.separator + fileUpload.getFileSHA256();
        File filePath = new File(path);
        if (!filePath.exists()) {
            filePath.mkdirs();
        }
        byte[] hash = FileSHA256.computeSHA256(file.getBytes());
        if (FileSHA256.bytesToHex(hash).equals(fileUpload.getChunkSHA256())) {
            FileOutputStream out = new FileOutputStream(path + File.separator +fileUpload.getChunkSHA256() + BaseConstant.FILE_SEPARATOR + fileUpload.getChunkIndex());
            out.write(file.getBytes());
            out.close();
            // 将文件哈希值存储到 Redis 中
            stringRedisTemplate.opsForHash().put(fileUpload.getFileSHA256(),fileUpload.getChunkIndex(), fileUpload.getChunkSHA256());
            // 为整个 Hash 设置过期时间
            stringRedisTemplate.expire(fileUpload.getFileSHA256(), 24, TimeUnit.HOURS);
        }
        return Result.success(fileUpload);
    }

    @PostMapping("/complete")
    public Result completeUpload(FileChunk fileUpload) {
        String property = System.getProperty(BaseConstant.USER_DIR);
        String path = property + File.separator + BaseConstant.PATH+ File.separator + fileUpload.getFileSHA256();
        try {
            // 检查文件目录
            File uploadDir = new File(path);
            if (!uploadDir.exists()) {
                return Result.fail("上传的切片不存在");
            }
            Map<Object, Object> entries =stringRedisTemplate.opsForHash().entries(fileUpload.getFileSHA256());
            List<String> fileList = mapToList(entries);
            if (StringUtils.isEmpty(fileUpload.getFileName())){
               fileUpload.setFileName(fileUpload.getFileSHA256());
           }
            // 合并切片文件
            File combinedFile = new File(path,fileUpload.getFileName());
            try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(combinedFile))) {
                for (int i = 0; i < fileList.size(); i++) {
                    String fileName = fileList.get(i)+ BaseConstant.FILE_SEPARATOR+i;
                    File chunkFile = new File(uploadDir, fileName);
                    try (DataInputStream dis = new DataInputStream(new FileInputStream(chunkFile))) {
                        byte[] buffer = new byte[1024];
                        int bytesRead;
                        while ((bytesRead = dis.read(buffer)) != -1) {
                            dos.write(buffer, 0, bytesRead);
                        }
                    } catch (IOException e) {
                        throw new RuntimeException("合并文件失败：" + e.getMessage());
                    }
                }
            }
            try (FileInputStream fileInputStream = new FileInputStream(combinedFile)){

                //对比文件hash
                String s = DigestUtils.sha256Hex(fileInputStream);

                if (!s.equals(fileUpload.getFileSHA256())) {
                    return Result.fail("文件校验失败");
                }
            }catch (IOException e){

            }
            // 删除临时切片文件
            deleteTemporaryFiles(path,fileList);
            //删除redis保存的文件hash
            stringRedisTemplate.delete(fileUpload.getFileSHA256());

            String s = uploadToMinio(fileUpload.getFileName(), combinedFile);
            return Result.success(s);
        } catch (Exception e) {
            e.printStackTrace();
            return Result.fail("合并失败：" + e.getMessage());
        }
    }

    @NotNull
    public String uploadToMinio(String name, File combinedFile) throws ErrorResponseException, InsufficientDataException, InternalException, InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, ServerException, XmlParserException {
        Date date = new Date(System.currentTimeMillis());
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String format = dateFormat.format(date);
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        // 构建文件名，格式为 日期/UUID@原始文件名。
        // 例如：2024-09-01/123e4567-e89b-12d3-a456-426614174000@example.txt。
        String fileName = format+"/"+uuid+"@"+ name;

        FileInputStream fileInputStream = new FileInputStream(combinedFile);
        minioClient.putObject(PutObjectArgs.builder()
                .bucket(minioPropertiesConfig.getBucketName())
                .object(fileName)
                .stream(fileInputStream, combinedFile.length(), -1)
                .contentType("application/octet-stream")
                .build());
        fileInputStream.close();
        String s = minioPropertiesConfig.getPublicNetwork() + "/" +
                minioPropertiesConfig.getBucketName() + "/" + fileName;
        return s;
    }

    private void deleteTemporaryFiles(String path, List<String> fileNames) {
        for (int i = 0; i < fileNames.size(); i++) {
                new File(path, fileNames.get(i)+ BaseConstant.FILE_SEPARATOR+i).delete();
        }
    }

    public static List<String> mapToList(Map<Object, Object> map) {
        List<String> list = new ArrayList<>(map.size());
        // 确保 List 的大小与 Map 的大小一致
        for (int i = 0; i < map.size(); i++) {
            list.add(null);
        }
        // 填充 List
        for (Map.Entry<Object, Object> entry : map.entrySet()) {
            int index = Integer.parseInt((String)entry.getKey());
            list.set(index, (String)entry.getValue());
        }
        return list;
    }




}
